Vite TypeScript Library Setup
A complete setup for creating TypeScript libraries that work in Node.js (CommonJS/ESM), browsers (UMD), and bundlers (ESM) with proper type declarations.
Key Requirements
- Support
require()
in Node.js CommonJS - Support
import
in Node.js ESM - Support
<script src="...">
in browsers - Support
import
in browser<script type="module">
- Support
import
with bundlers (Vite, Webpack, etc.) - Proper TypeScript declarations for all environments
Essential Files
1. package.json
{
"name": "my-library",
"version": "1.0.0",
"type": "module",
"files": ["dist"],
"main": "./dist/my-library.cjs",
"module": "./dist/my-library.js",
"browser": "./dist/my-library.umd.cjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/my-library.js"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/my-library.cjs"
},
"browser": {
"types": "./dist/index.d.ts",
"default": "./dist/my-library.umd.cjs"
}
}
},
"scripts": {
"build": "vite build",
"dev": "vite build --watch"
},
"devDependencies": {
"@types/node": "^22.15.30",
"typescript": "^5.8.3",
"vite": "^6.0.0",
"vite-plugin-dts": "^4.3.0"
}
}
2. vite.config.ts
import { defineConfig } from 'vite';
import { resolve } from 'path';
import dts from 'vite-plugin-dts';
export default defineConfig({
plugins: [
dts({
include: 'src/**/*',
exclude: 'src/**/*.(spec|test).(js|ts)',
rollupTypes: true, // 🔑 KEY: Bundle all types into single file
outDir: 'dist',
}),
],
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'MyLibrary', // Global variable name for UMD
fileName: 'my-library',
formats: ['es', 'cjs', 'umd'],
},
rollupOptions: {
external: [], // Add external deps here if needed
output: {
exports: 'named',
},
},
},
});
3. tsconfig.json
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"moduleResolution": "node",
"declaration": true,
"outDir": "dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]
}
Critical Configuration Points
🔑 rollupTypes: true
THE KEY SETTING - Without this:
- ❌ Generates separate
.d.ts
files maintaining directory structure - ❌ Need complex package.json exports like
"./types": "./dist/types/index.d.ts"
- ❌ Users import with
import type { Type } from 'my-library/types'
With rollupTypes: true
:
- ✅ All types bundled into single
dist/index.d.ts
- ✅ Simple package.json with just
"types": "./dist/index.d.ts"
- ✅ Clean imports:
import type { Type } from 'my-library'
Export Order in package.json
"exports": {
".": {
"import": { ... }, // Modern ESM first
"require": { ... }, // CommonJS second
"browser": { ... }, // Most specific last
}
}
File Extensions
- ESM:
.js
(because"type": "module"
) - CommonJS:
.cjs
- UMD:
.umd.cjs
Build Output
dist/
├── index.d.ts # 🎯 All TypeScript declarations
├── my-library.js # ESM format
├── my-library.cjs # CommonJS format
└── my-library.umd.cjs # UMD format for browsers
Usage Examples
Node.js CommonJS:
const { myFunction } = require('my-library');
Node.js ESM:
import { myFunction } from 'my-library';
Browser Script Tag:
<script src="./dist/my-library.umd.cjs"></script>
<script>
console.log(MyLibrary.myFunction());
</script>
Browser ESM:
<script type="module">
import { myFunction } from './dist/my-library.js';
console.log(myFunction());
</script>
TypeScript (any environment):
import type { MyType } from 'my-library';
import { myFunction } from 'my-library';
Common Pitfalls
- Missing
vite-plugin-dts
→ No TypeScript declarations - Missing
rollupTypes: true
→ Complex directory-based type exports - Wrong export order → Module resolution issues
- Missing
"type": "module"
→ Import/export confusion - Wrong file extensions → Runtime errors
Commands
# Build once
npm run build
# Watch mode for development
npm run dev
# Install in another project
npm install /path/to/your/library
This setup provides a simple, clean library that "just works" everywhere without complex configuration! 🎉